﻿using UnityEngine;
using System.Collections;

namespace RootMotion.Dynamics
{

    /// <summary>
    /// Dynamic pin, drag and mapping weight control based on collisions with objects.
    /// </summary>
    public class PuppetControllerLite : MonoBehaviour, ICollisionEventListener
    {

        [System.Serializable]
        public class Group
        {
            public string name;
            [Tooltip("The muscle groups to apply this pinWeightMlp and muscleWeightMlp to.")]
            public int[] indices = new int[0];
            [Range(0f, 1f)] public float pinWeightMlp = 0.5f;
            [Range(0f, 1f)] public float muscleWeightMlp = 0.5f;

            [Tooltip("When the puppet is touched, sets muscle Rigidbody drag to this value to reduce the rubber chicken effect.")]
            public float drag = 2f;
            [Tooltip("The time of blending in this script's effects when the puppet is touched.")]
            public float blendInTime = 0.05f;
            [Tooltip("The time of blending out this script's effects when the puppet is not touched any more.")]
            public float blendOutTime = 1f;

            public bool enabled { get; private set; }
            public float mappingWeight { get; private set; }

            private float dam = 0f;
            private float damTime = -100f;
            private float damV;
            private float map, mapV;
            
            public void TryDamage(Collision collision, CollisionEventBroadcaster broadcaster)
            {
                bool included = false;
                for (int i = 0; i < indices.Length; i++)
                {
                    if (broadcaster.muscle.index == indices[i])
                    {
                        included = true;
                        break;
                    }
                }
                if (!included) return;

                damTime = Time.time;
                enabled = true;
            }

            public void Update(PuppetMasterLite puppetMaster)
            {
                if (!enabled) return;

                // Dynamically adjust drag, pin and mapping weights based on collisions so we can have perfect animation until there is a collision
                //dragW = Mathf.MoveTowards(dragW, 1f, Time.deltaTime * 2f);

                bool unpinned = puppetMaster.pinWeight <= 0f;

                float damTarget = Time.time > damTime + 0.2f ? 0f : 1f;
                if (unpinned) damTarget = 1f;

                float mapTarget = damTarget;
                if (unpinned) mapTarget = 1f;

                float sDampTime = damTarget > dam ? blendInTime : blendOutTime;
                dam = Mathf.SmoothDamp(dam, damTarget, ref damV, sDampTime);
                if (damTarget < dam && dam < 0.001f) dam = 0f;

                float mDampTime = mapTarget > map ? blendInTime : blendOutTime;
                map = Mathf.SmoothDamp(map, mapTarget, ref mapV, mDampTime);
                if (mapTarget < map && map < 0.001f) map = 0f;

                if (unpinned) dam = Mathf.Min(dam, map);

                float d = unpinned ? 0f : drag * map;
                float angularD = unpinned ? 0.05f : drag * map;
                mappingWeight = Mathf.Lerp(0f, 1f, map);

                for (int i = 0; i < indices.Length; i++)
                {
                    int index = indices[i];
                    puppetMaster.muscles[index].pinWeightMlp = Mathf.Lerp(1f, pinWeightMlp, dam);
                    puppetMaster.muscles[index].muscleWeightMlp = Mathf.Lerp(1f, muscleWeightMlp, dam);

                    puppetMaster.muscles[index].rigidbody.linearDamping = d;
                    puppetMaster.muscles[index].rigidbody.angularDamping = angularD;

                    //puppetMaster.muscles[index].mappingWeightMlp = mappingWeight;
                }

                if (dam <= 0f && map < 0f) enabled = false;
            }
        }

        public PuppetMasterLite puppetMaster;
        public LayerMask collisionLayers;
        
        [Tooltip("When the puppet is touched, sets pin weight and muscle weight values for these groups.")]
        public Group[] groups = new Group[0];       

        void Start()
        {
            foreach (MuscleLite m in puppetMaster.muscles)
            {
                var b = m.joint.gameObject.AddComponent<CollisionEventBroadcaster>();
                b.listener = this;
                b.muscle = m;
            }
        }

        private bool NeedToUpdate()
        {
            foreach (Group group in groups)
            {
                if (group.enabled) return true;
            }
            return false;
        }

        void FixedUpdate()
        {
            if (!NeedToUpdate()) return;

            float maxMappingWeight = 0f;

            foreach (Group group in groups)
            {
                group.Update(puppetMaster);
                maxMappingWeight = Mathf.Max(maxMappingWeight, group.mappingWeight);
            }

            foreach (MuscleLite m in puppetMaster.muscles)
            {
                m.mappingWeightMlp = maxMappingWeight;
            }
        }

        // Called by CollisionEventBroadcaster when it collides with something
        public void OnCollisionEnterEvent(Collision collision, CollisionEventBroadcaster broadcaster)
        {
            ProcessCollisionEvent(collision, broadcaster);
        }

        public void OnCollisionStayEvent(Collision collision, CollisionEventBroadcaster broadcaster)
        {
            ProcessCollisionEvent(collision, broadcaster);
        }

        private void ProcessCollisionEvent(Collision collision, CollisionEventBroadcaster broadcaster)
        {
            if (collision.collider.transform.root == transform) return;
            if (!LayerMaskExtensions.Contains(collisionLayers, collision.collider.gameObject.layer)) return;
            
            foreach (Group group in groups)
            {
                group.TryDamage(collision, broadcaster);
            }
        }

        public void OnCollisionExitEvent(Collision collision, CollisionEventBroadcaster broadcaster) {}
    }
}
